package com.hyt.it.ogt.kq.service.gov.schedule.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.ImmutablePair;

import com.hyt.common.utils.string.StringUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.impl.support.CandidateGroup;
import com.hyt.it.ogt.kq.service.gov.schedule.model.Candidate;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ExamRoom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GroupUtils {
    private static final Logger logger = LoggerFactory.getLogger(GroupUtils.class);

    /**
     * 根据分组属性对考场进行分组
     */
    public static Map<String, List<ExamRoom>> groupExamRoomsByAttrs(List<ExamRoom> examRooms, List<String> groupAttrs) {
        Map<String, List<ExamRoom>> groupMap = new HashMap<>();
        List<Field> examRoomGroupFields = ReflectionUtils.getClassFieldIgnoreCase(groupAttrs, ExamRoom.class);
        examRooms.forEach(item -> {
            String groupName = combineGroupName(examRoomGroupFields, item);
            if (groupAttrs.size() == 1) {
                // 只包括考场应答方式
                groupName = String.format("%s|%s", groupName, "default");
            }

            if (!groupMap.containsKey(groupName)) {
                groupMap.put(groupName, new ArrayList<ExamRoom>());
            }
            groupMap.get(groupName).add(item);
        });
        return groupMap;
    }

    /**
     * 根据分组属性对考生进行分组, 针对不需要保持考生的坐位分组
     * 
     * @param fields
     * @param object
     * @return
     */
    public static Map<String, List<Candidate>> groupCandidateByAttrs(List<Candidate> candidates,
            List<String> groupAttrs) {
        Map<String, List<Candidate>> groupMap = new HashMap<>();
        List<Field> candidateGroupFields = ReflectionUtils.getClassFieldIgnoreCase(groupAttrs, Candidate.class);
        candidates.forEach(item -> {
            String groupName = combineGroupName(candidateGroupFields, item);
            if (groupAttrs.size() == 1) {
                // 分组参数只包括考场应答方式
                groupName = String.format("%s|%s", groupName, "default");
            } else if (groupAttrs.size() == 2 && groupAttrs.contains("subject")) {
                // 分组参数只包括考场应答方式和考试科目
                String[] splitGroupNames = groupName.split("\\|");
                groupName = String.format("%s|%s|%s", splitGroupNames[0], "default", splitGroupNames[1]);
            }

            if (!groupMap.containsKey(groupName)) {
                groupMap.put(groupName, new ArrayList<Candidate>());
            }
            groupMap.get(groupName).add(item);
        });
        return groupMap;
    }

    /**
     * 根据分组属性对考生进行分组, 针对需要保持考生的坐位的分组，注意需要保持考生坐位的
     * 情况，需要将考生各批次科目累计起来，组成如下形式
     * A考生 (语文|生物) B考生 (语文|生物) F考生 (ANY|生物) D考生 (语文|化学)
     * 
     * @param fields
     * @param object
     * @return
     */
    public static Map<String, List<Candidate>> groupNeedMaintainPositionCandidateByAttrs(List<Candidate> candidates,
            List<String> groupAttrs, Map<String, Map<String, Candidate>> totalExamCandidateMap,
            Map<String, BitSet> candidateBatchFeatureMap) {
        Map<String, List<Candidate>> groupMap = new HashMap<>();
        List<Field> candidateGroupFields = ReflectionUtils.getClassFieldIgnoreCase(groupAttrs, Candidate.class);
        List<String> examBatchNos = new ArrayList<>(totalExamCandidateMap.keySet());
        Collections.sort(examBatchNos);

        for (Candidate item : candidates) {
            // 取得考生不包括科目的组名
            String groupName = combineGroupName(candidateGroupFields, item);

            if (groupAttrs.size() == 1) {
                // 分组参数只包括考场应答方式
                groupName = String.format("%s|%s", groupName, "default");
            } else if (groupAttrs.size() == 2 && groupAttrs.contains("subject")) {
                // 分组参数只包括考场应答方式和考试科目
                String[] splitGroupNames = groupName.split("\\|");
                groupName = String.format("%s|%s|%s", splitGroupNames[0], "default", splitGroupNames[1]);
            }

            // 根据考生批次号特征码取得对应批次的考试科目
            BitSet featureBitSet = candidateBatchFeatureMap.get(item.getBmStudentId());
            for (int index = 0; index < examBatchNos.size(); index++) {
                if (featureBitSet.get(index)) {
                    groupName = groupName + "|" + totalExamCandidateMap.get(examBatchNos.get(index))
                            .get(item.getBmStudentId()).getSubject();
                } else {
                    groupName = groupName + "|ANY";
                }
            }

            if (!groupMap.containsKey(groupName)) {
                groupMap.put(groupName, new ArrayList<Candidate>());
            }
            groupMap.get(groupName).add(item);
        }

        return groupMap;
    }

    /**
     * 拼接组合名
     * 
     * @param fields
     * @param object
     * @return
     */
    private static String combineGroupName(List<Field> fields, Object object) {
        String groupName = "default";
        List<String> fieldValues = new ArrayList<>();
        fields.forEach(item -> {
            String filedValue = (String) ReflectionUtils.getFieldValue(item, object);
            if (!Objects.isNull(filedValue) && filedValue.length() > 0) {
                fieldValues.add(filedValue);
            }
        });
        if (fieldValues.size() > 0) {
            groupName = StringUtils.join(fieldValues, "|");
        }
        return groupName;
    }

    public static CandidateGroup findSameGroupName(String groupName, List<CandidateGroup> candidateGroups) {
        for (CandidateGroup item : candidateGroups) {
            if (item.getGroupName().equalsIgnoreCase(groupName)) {
                return item;
            }
        }
        return null;
    }

    /**
     * 根据特征码，比较两组的相似度
     * 
     * @param subject
     * @param objects
     * @return 相似度，组
     */
    public static ImmutablePair<Integer, CandidateGroup> findSuitCandidateByFeatureCode(BitSet featureCode,
            List<CandidateGroup> objects) {
        int bestSuitNumber = 0;
        CandidateGroup bestCGroup = null;
        for (CandidateGroup candidateGroup : objects) {
            BitSet bitSet = (BitSet) featureCode.clone();
            bitSet.and(candidateGroup.getFeatureCode());
            if (bitSet.cardinality() > bestSuitNumber) {
                bestSuitNumber = bitSet.cardinality();
                bestCGroup = candidateGroup;
            }
        }
        return ImmutablePair.of(bestSuitNumber, bestCGroup);
    }

    /**
     * 返回考生数量最接近的分组
     * 
     * @param candidateNumber
     * @param objects
     * @return
     */
    public static CandidateGroup findSuitCandidateByCandidateNumber(int candidateNumber, List<CandidateGroup> objects) {
        List<CandidateGroup> sortedObjects = objects.stream()
                .sorted(Comparator.comparingInt(item -> {
                    return Math.abs(candidateNumber - item.getCandidateNumber());
                })).collect(Collectors.toList());
        return sortedObjects.get(0);
    }

    public static Set<String> mergeGroupName(Set<String> groupNameSet) {
        Set<String> mergeGroupNameSet = new HashSet<>();
        for (String groupName : groupNameSet) {
            if (groupName.indexOf("ANY") == -1) {
                mergeGroupNameSet.add(groupName);
            }
        }

        List<String> groupNameList = groupNameSet.stream().sorted(Comparator.comparingInt(item -> {
            return countSpecialTag(item, "ANY");
        })).collect(Collectors.toList());

        for (String groupName : groupNameList) {
            if (groupName.indexOf("ANY") != -1) {
                ImmutablePair<String, String> result = SimilarUtils.findCanReplaceKey(groupName,
                        new ArrayList<>(mergeGroupNameSet));
                if (result == null) {
                    mergeGroupNameSet.add(groupName);
                }
            }
        }
        return mergeGroupNameSet;
    }

    public static int countSpecialTag(String text, String special) {
        int count = 0;
        while (text.indexOf(special) >= 0) {
            text = text.substring(text.indexOf(special) + special.length());
            count++;
        }
        return count;
    }

    /**
     * 对考生分组进行合并，合并原则 ：合并原则 ： A|ANY|B + A|C|B == A|C|B
     */
    public static Map<String, List<Candidate>> mergeCandidateGroup(Map<String, List<Candidate>> candidateGroupMap) {
        Map<String, List<Candidate>> mergeCandidateGroup = new HashMap<>();
        // 将不包含ANY的分组直接增加到mergeCandidateGroup
        for (Map.Entry<String, List<Candidate>> entry : candidateGroupMap.entrySet()) {
            if (entry.getKey().indexOf("ANY") == -1) {
                mergeCandidateGroup.put(entry.getKey(), entry.getValue());
            }
        }

        for (Map.Entry<String, List<Candidate>> entry : candidateGroupMap.entrySet()) {
            if (entry.getKey().indexOf("ANY") != -1) {
                ImmutablePair<String, String> result = SimilarUtils.findCanReplaceKey(entry.getKey(),
                        new ArrayList<>(mergeCandidateGroup.keySet()));
                if (result != null) {
                    mergeCandidateGroup.get(result.getLeft()).addAll(entry.getValue());
                } else {
                    mergeCandidateGroup.put(entry.getKey(), entry.getValue());
                }
            }
        }

        return mergeCandidateGroup;
    }

    /**
     * 查找可以组合在一起的考生
     * 
     * @param candidates
     * @param featureCodeMap
     */
    public static List<List<Candidate>> findCanTotegherCandidate(List<Candidate> candidateList,
            Map<String, BitSet> featureCodeMap, int batchNumber, Set<String> cacheFilterIndexs) {
        List<List<Candidate>> toteghterCandidateList = new ArrayList<>();
        List<Candidate> candidates = candidateList.stream()
                .sorted(Comparator
                        .comparing(item -> featureCodeMap.get(((Candidate) item).getBmStudentId()).cardinality())
                        .reversed())
                .collect(Collectors.toList());
        List<BitSet> candidateBatchFeatures = new ArrayList<>();

        candidates.stream().forEach(item -> {
            candidateBatchFeatures.add(featureCodeMap.get(item.getBmStudentId()));
        });
        logger.debug("candidate batch features size :{}", candidateBatchFeatures.size());

        // Set<Integer> cacheFilterIndexs = new
        // HashSet<>(candidateBatchFeatures.size());
        for (int i = 0; i < candidateBatchFeatures.size(); i++) {
            if (i % 100 == 0) {
                logger.debug("find best match together candidate current number :{} total number :{}", i,
                        candidateBatchFeatures.size());
            }
            if (cacheFilterIndexs.contains(candidates.get(i).getBmStudentId())) {
                continue;
            }

            Set<Candidate> totegherCandidates = new HashSet<>();
            totegherCandidates.add(candidates.get(i));
            cacheFilterIndexs.add(candidates.get(i).getBmStudentId());

            BitSet currentIdxBitSetClone = (BitSet) featureCodeMap.get(candidates.get(i).getBmStudentId()).clone();

            List<Integer> featureIndexs = BitSetUtils.andBitSet2Indexs(i, candidateBatchFeatures);
            for (int cIdx : featureIndexs) {
                if (currentIdxBitSetClone.cardinality() == batchNumber) {
                    break;
                }
                if (cacheFilterIndexs.contains(candidates.get(cIdx).getBmStudentId())) {
                    continue;
                }

                BitSet relCandidateBitSet = featureCodeMap.get(candidates.get(cIdx).getBmStudentId());

                if (currentIdxBitSetClone.intersects(relCandidateBitSet)) {
                    continue;
                }
                currentIdxBitSetClone.or(relCandidateBitSet);
                totegherCandidates.add(candidates.get(cIdx));
                cacheFilterIndexs.add(candidates.get(cIdx).getBmStudentId());
            }
            toteghterCandidateList.add(new ArrayList<>(totegherCandidates));
        }
        return toteghterCandidateList;
    }

    public static Set<Integer> togetherCandidateByBitsets(int selfIndex, List<BitSet> otherBitSets) {
        Set<Integer> togetherIdxs = new HashSet<>();
        togetherIdxs.add(selfIndex);
        if (otherBitSets.size() == 1) {
            return togetherIdxs;
        }

        BitSet bitSet = otherBitSets.get(0);
        int bestMatchNumber = 0;
        BitSet bestMatchBitSet = null;
        for (int i = 1; i < otherBitSets.size(); i++) {
            BitSet bitSetClone = (BitSet) bitSet.clone();
            bitSetClone.and(otherBitSets.get(i));
            if (bitSetClone.cardinality() > bestMatchNumber) {
                bestMatchNumber = bitSetClone.cardinality();
                bestMatchBitSet = (BitSet) otherBitSets.get(i).clone();
            }
        }
        if (bestMatchBitSet != null) {
            bestMatchBitSet.and(bitSet);
            int[] matchIndex = BitSetUtils.convertBitSet2Index(bestMatchBitSet);
            for (int item : matchIndex) {
                if (item != selfIndex) {
                    togetherIdxs.add(item);
                    break;
                }
            }
        }
        return togetherIdxs;
    }
}
